package pl.poznan.put.cs.to.tsp.hea;

import java.util.Calendar;
import java.util.LinkedList;
import java.util.Random;

import pl.poznan.put.cs.to.tsp.common.ConstantsHolder;
import pl.poznan.put.cs.to.tsp.common.FileOperations;
import pl.poznan.put.cs.to.tsp.common.IResultHolder;
import pl.poznan.put.cs.to.tsp.common.Vertexes;
import pl.poznan.put.cs.to.tsp.heuristics.NormalHeuristic;
import pl.poznan.put.cs.to.tsp.listeners.EmptyTSPListener;
import pl.poznan.put.cs.to.tsp.listeners.TSPListener;
import pl.poznan.put.cs.to.tsp.localsearch.LocalSearch;
import pl.poznan.put.cs.to.tsp.sorted.SortedResult;

public class HybridEvolutionaryAlgorithm implements IResultHolder {
	private PopulationManager popManager;
	private int workTime;
	private int instanceSize;
	private Vertexes vertexes;
	private int populationSize;
	private TSPListener tspListener;
	private Random random;

	public TSPListener getTspListener() {
		return tspListener;
	}

	public void setTspListener(TSPListener tspListener) {
		this.tspListener = tspListener;
	}

	private TSPListener emptyListener;

	public HybridEvolutionaryAlgorithm(String inputFile, TSPListener tspListener) {
		this.random = new Random();
		long start = Calendar.getInstance().getTimeInMillis();
		long now;

		this.emptyListener = new EmptyTSPListener();
		// this.workTime = workTime;
		this.vertexes = FileOperations.loadInstance(inputFile);
		this.instanceSize = this.vertexes.getNumberOfNodes();

		this.workTime = this.decideOnWorkTime(this.instanceSize);
		//System.out.println(this.workTime);

		this.populationSize = this.calculatePopSize(this.instanceSize,
				this.workTime);
		this.popManager = new PopulationManager(this.populationSize);
		this.tspListener = tspListener;

		this.generateStartPopulation();

		now = Calendar.getInstance().getTimeInMillis();
		// System.out.println("Population generated in: "
		// + (now - start)
		// + ", best: "
		// + (this.popManager.getBest().getLengthA() + this.popManager
		// .getBest().getLengthB()));
		if ((now - start) >= this.workTime) {
			return;
		}

		int failCounter = 0;

		int iterations = 0;

		do {
			IResultHolder r1 = this.popManager.drawResult(null);
			IResultHolder r2 = this.popManager.drawResult(r1);

			HEAResultCrosser rc = new HEAResultCrosser(r1, r2, this.vertexes);
			LocalSearch ls = new LocalSearch(rc, this.emptyListener);
			SortedResult sr = new SortedResult(ls);
			boolean better = this.popManager.addToPopulation(sr);

			if (better) {
				this.tspListener.solutionGenerated(this.getListA(), this
						.getListB(), this.vertexes);
				// System.out
				// .println((this.popManager.getBest().getLengthA() +
				// this.popManager
				// .getBest().getLengthB())
				// + " | " + (sr.getLengthA() + sr.getLengthB()));
				failCounter = 0;
			} else {
				failCounter++;
				if (failCounter == ConstantsHolder.MAX_MISSES) {
					this.mutate();
					failCounter = 0;
				}
			}

			// System.out.println(this.popManager.getPopulation());
			// System.out.println((sr.getLengthA() + sr.getLengthB()));
			now = Calendar.getInstance().getTimeInMillis();
			// System.out.println("iteration time: " + (now - iterationStart));
			iterations++;
		} while ((now - start) <= this.workTime);

		FileOperations.saveSolutionToFile(this, ConstantsHolder.SOL_FILE);
		FileOperations.saveResultToFile(this, ConstantsHolder.RES_FILE);

		// System.out.println("[HEA] Performed " + iterations + " iterations.");
	}

	private int decideOnWorkTime(int size) {
		if (size <= 100) {
			return 29500;
		}
		if (size <= 150) {
			return 59500;
		}
		return 89500;

	}

	public HybridEvolutionaryAlgorithm(String inputFile, String outputFile,
			int workTime, TSPListener tspListener) {
		this.random = new Random();
		long start = Calendar.getInstance().getTimeInMillis();
		long now;

		this.emptyListener = new EmptyTSPListener();
		this.workTime = workTime;
		this.vertexes = FileOperations.loadInstance(inputFile);
		this.instanceSize = this.vertexes.getNumberOfNodes();
		this.populationSize = this.calculatePopSize(this.instanceSize,
				this.workTime);
		this.popManager = new PopulationManager(this.populationSize);
		this.tspListener = tspListener;

		this.generateStartPopulation();

		now = Calendar.getInstance().getTimeInMillis();
		// System.out.println("Population generated in: "
		// + (now - start)
		// + ", best: "
		// + (this.popManager.getBest().getLengthA() + this.popManager
		// .getBest().getLengthB()));
		if ((now - start) >= this.workTime) {
			return;
		}

		int failCounter = 0;

		int iterations = 0;

		do {
			IResultHolder r1 = this.popManager.drawResult(null);
			IResultHolder r2 = this.popManager.drawResult(r1);

			HEAResultCrosser rc = new HEAResultCrosser(r1, r2, this.vertexes);
			LocalSearch ls = new LocalSearch(rc, this.emptyListener);
			SortedResult sr = new SortedResult(ls);
			boolean better = this.popManager.addToPopulation(sr);

			if (better) {
				this.tspListener.solutionGenerated(this.getListA(), this
						.getListB(), this.vertexes);
				// System.out
				// .println((this.popManager.getBest().getLengthA() +
				// this.popManager
				// .getBest().getLengthB())
				// + " | " + (sr.getLengthA() + sr.getLengthB()));
				failCounter = 0;
			} else {
				failCounter++;
				if (failCounter == ConstantsHolder.MAX_MISSES) {
					this.mutate();
					failCounter = 0;
				}
			}

			// System.out.println(this.popManager.getPopulation());
			// System.out.println((sr.getLengthA() + sr.getLengthB()));
			now = Calendar.getInstance().getTimeInMillis();
			// System.out.println("iteration time: " + (now - iterationStart));
			iterations++;
		} while ((now - start) <= this.workTime);

		// System.out.println("[HEA] Performed " + iterations + " iterations.");
	}

	private void mutate() {
		this.popManager.killPercentOfPopulation(ConstantsHolder.MUTATION_PART);
		boolean cont = true;
		while (cont) {
			IResultHolder generated = this.generateSortedResult();
			HEAResultCrosser rc = new HEAResultCrosser(this.popManager
					.getPopulation().get(
							this.random.nextInt(this.popManager.getPopulation()
									.size())), generated, this.vertexes);

			cont = this.popManager.addToPopulation(rc);
		}
	}

	private int calculatePopSize(int instanceSize, int workTime) {
		return ConstantsHolder.POP_SIZE;
	}

	private IResultHolder runHeuristic() {
		// return new ExpandingHeuristic(this.vertexes, this.emptyListener);
		return new NormalHeuristic(this.vertexes, this.emptyListener);
		// return new RandomHeuristic(this.vertexes, this.emptyListener);
	}

	private IResultHolder runLocalSearch() {
		IResultHolder heuristic = this.runHeuristic();
		return new LocalSearch(heuristic, this.emptyListener);
	}

	private IResultHolder generateSortedResult() {
		IResultHolder ls = this.runLocalSearch();
		return new SortedResult(ls);
	}

	private void generateStartPopulation() {
		for (int i = 0; i < this.populationSize; i++) {
			this.popManager.addToPopulation(this.generateSortedResult());
			this.tspListener.solutionGenerated(this.getListA(),
					this.getListB(), this.vertexes);
		}
	}

	@Override
	public int getLengthA() {
		return this.popManager.getBest().getLengthA();
	}

	@Override
	public int getLengthB() {
		return this.popManager.getBest().getLengthB();
	}

	@Override
	public LinkedList<Integer> getListA() {
		return this.popManager.getBest().getListA();
	}

	@Override
	public LinkedList<Integer> getListB() {
		return this.popManager.getBest().getListB();
	}

	@Override
	public Vertexes getVertexes() {
		return this.vertexes;
	}

	@Override
	public int compareTo(IResultHolder o) {
		int local = this.getLengthA() + this.getLengthB();
		int other = o.getLengthA() + o.getLengthB();
		return local - other;
	}

}